/****************************************************************************************
**  File: Framer.hpp
**  Author: Asteroth
**  Date: Apr 27, 2009
****************************************************************************************/

#ifndef __ZEN_FRAMER_HPP_
#define __ZEN_FRAMER_HPP_

#include <time.h>

#include <zen/defines.hpp>

namespace zen {

	namespace sys {

		const int FRAMER_CONTINUE       = 0;
		const int FRAMER_EXIT           = 1;

		class Framer {
			private:
				int UPS, DPS;
				int rUPS, rDPS, rSLP;

				int rcUPS, rcDPS, lastPS;
				clock_t clk;
				int interval;

			public:
				Framer() {
					UPS = 25; DPS = 25;
					rUPS = UPS; rDPS = DPS;
				}

				Framer(int ups, int dps) {
					UPS = ups > 1 ? ups : 2; DPS = dps > 1 ? dps : 2;
					rUPS = UPS; rDPS = DPS;
				}

				virtual ~Framer() { }

				/// Initialize is called once at the beginning.
				virtual int initialize() = 0;
				/// Update is called every time the interval of update time finishes. The cycle
				/// finishes when update or draw returns FRAMER_EXIT.
				virtual int update() = 0;
				/// Draw is called every time the interval of draw time finishes. The cycle
				/// finishes when update or draw returns FRAMER_EXIT.
				virtual int draw() = 0;
				/// Finalize is called once at the ending.
				virtual int finalize() = 0;

				/**
				 * Set updates per second limit.
				 */
				inline void setUPS(int ups) {
					UPS = ups > 1 ? ups : 2;
					rUPS = UPS;
				}

				/**
				 * Set draws per second limit.
				 */
				inline void setDPS(int dps) {
					DPS = dps > 1 ? dps : 2;
					rDPS = DPS;
				}

				/**
				 * Updates per second limit.
				 */
				inline int getUPS() { return UPS; }

				/**
				 * Draws per second limit.
				 */
				inline int getDPS() { return DPS; }

				/**
				 * Counter of real update times of the last second.
				 */
				inline int getRealUPS() { return rUPS; }

				/**
				 * Counter of real draw times of the last second.
				 */
				inline int getRealDPS() { return rDPS; }

				/**
				 * Get the clock of the last update call.
				 */
				inline clock_t getClock() { return clk; }

				/**
				 * Get the clock interval of the last two update calls.
				 */
				inline int getInterval() { return interval; }

				/// Execute is the core function, it calls initialize then calls update and draw
				/// every interval until one of them returns FRAMER_EXIT, then calls finalize.
				int execute() {
					bool goOn = true;
					float luTime = 0;
					float ldTime = 0;
					float uInterval;
					float dInterval;

					lastPS = 0;
					rcUPS = rcDPS = 0;

					clk = zen_clock();
					interval = 0;

					int ret = initialize();
					goOn = ret == FRAMER_CONTINUE;

					while (goOn) {
						uInterval = 1000.0f / (float)UPS;
						dInterval = 1000.0f / (float)DPS;

						interval = clk;
						clk = zen_clock();
						interval = clk - interval;
						bool sleep = true;

						if (clk > luTime + uInterval) {
							luTime += uInterval;
							rcUPS++;
							ret = update();
							goOn = (ret == FRAMER_CONTINUE);
							sleep = false;
						}
						if (goOn && clk > ldTime + dInterval) {
							ldTime += dInterval;
							rcDPS++;
							ret = draw();
							goOn = (ret == FRAMER_CONTINUE);
							sleep = false;
						}
						if (sleep) {
                			zen_sleep(1);
						}

						int ps = clk / 1000;
						if (lastPS < ps) {
							lastPS = ps;
							rUPS = rcUPS;
							rDPS = rcDPS;
							rcUPS = rcDPS = 0;
						}
					}

					ret = finalize();
					return ret;
				}

		};

	} // namespace sys

} // namespace zen

#endif
